﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Client.cs" company="">
//   
// </copyright>
// <summary>
// The logic of the game.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

/*
 * Author: Juan Mompean Esteban, Mikkel Thordal
 * Created: 17-11-2011
 */

namespace ITU_vs_Zombies.Game
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;

    using ITU_vs_Zombies.Character;
    using ITU_vs_Zombies.Graphics;
    using ITU_vs_Zombies.Menu;
    using ITU_vs_Zombies.Scenery;
    using ITU_vs_Zombies.Stuff;
    using ITU_vs_Zombies.UsersInfo;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Storage;

    /// <summary>
    /// The client control the logic of the game and contains all the objects.
    /// </summary>
    public static class Client
    {
        #region Constants and Fields

        /// <summary>
        /// The container name.
        /// </summary>
        public const string ContainerName = "ItuVsZombiesSavedGamed";

        /// <summary>
        /// The filename.
        /// </summary>
        public const string Filename = "mysave.sav";

        /// <summary>
        /// The active weapon container.
        /// </summary>
        public static readonly ActiveWeaponContainer ActiveWeaponContainer = new ActiveWeaponContainer();

        /// <summary>
        /// It indicates when the game is loaded.
        /// </summary>
        public static bool IsLoaded;

        /// <summary>
        /// The difficulty.
        /// </summary>
        private static uint difficulty;

        /// <summary>
        /// The enemy spawn time.
        /// </summary>
        private static TimeSpan enemySpawnTime = TimeSpan.FromSeconds(10.0f);

        /// <summary>
        /// The floors.
        /// </summary>
        private static List<Floor> floors = new List<Floor>();

        /// <summary>
        /// The game time.
        /// </summary>
        private static TimeSpan gameTime;

        /// <summary>
        /// The previous spawn time.
        /// </summary>
        private static TimeSpan previousSpawnTime = TimeSpan.Zero;

        /// <summary>
        /// The score font.
        /// </summary>
        private static SpriteFont scoreFont;

        /// <summary>
        /// The screen width.
        /// </summary>
        private static int screenWidth;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets Content.
        /// </summary>
        public static ContentManager Content { get; set; }

        /// <summary>
        /// Gets or sets Device.
        /// </summary>
        public static StorageDevice Device { get; set; }

        /// <summary>
        /// Gets or sets Difficulty.
        /// </summary>
        public static uint Difficulty
        {
            get
            {
                return difficulty;
            }

            set
            {
                difficulty = value;
                enemySpawnTime = TimeSpan.FromSeconds(10.0f / Math.Max(1.0f, difficulty));
            }
        }

        /// <summary>
        /// Gets or sets the elevator.
        /// </summary>
        public static Elevator Elevator { get; set; }

        /// <summary>
        /// Gets or sets Floors.
        /// </summary>
        public static List<Floor> Floors
        {
            get
            {
                Contract.Ensures(Contract.Result<List<Floor>>() != null);
                if (floors == null)
                {
                    floors = new List<Floor>();
                }

                return floors;
            }

            set
            {
                floors = value;
            }
        }

        /// <summary>
        /// Gets FloorsNumber.
        /// </summary>
        public static uint FloorsNumber
        {
            get
            {
                return (uint)Floors.Count;
            }
        }

        /// <summary>
        /// Gets or sets GameState.
        /// </summary>
        public static GameStateEnum GameState { get; set; }

        /// <summary>
        /// Gets or sets GameTime.
        /// </summary>
        public static GameTime GameTime { get; set; }

        /// <summary>
        /// Gets or sets GraphicsDevice.
        /// </summary>
        public static GraphicsDevice GraphicsDevice { get; set; }

        /// <summary>
        /// Gets or sets MainPlayer.
        /// </summary>
        public static Player MainPlayer { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The add floor.
        /// </summary>
        /// <param name="floor">
        /// The floor.
        /// </param>
        public static void AddFloor(Floor floor)
        {
            Contract.Requires(floor != null);
            Contract.Requires(FloorsNumber < 6);
            Contract.Ensures(Contract.OldValue(FloorsNumber) + 1 == FloorsNumber);
            Floors.Add(floor);
            floor.Initialize();
        }

        /// <summary>
        /// The dispose.
        /// </summary>
        public static void Dispose()
        {
            Floors.ForEach(floor => floor.Dispose());
            Floors.Clear();
            ActiveWeaponContainer.Dispose();
        }

        /// <summary>
        /// The draw.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        public static void Draw(SpriteBatch spriteBatch)
        {
            Contract.Requires(Elevator != null);
            foreach (Floor floor in Floors.Where(f => f.Active))
            {
                floor.Draw(spriteBatch);
                DrawTexts(spriteBatch);
                DrawActiveWeapon(spriteBatch);
            }

            Elevator.Draw(spriteBatch);
        }

        /// <summary>
        /// The load content.
        /// </summary>
        public static void LoadContent()
        {
            LoadDynamicContent();

            Elevator = new Elevator("elevator", 1);
            Elevator.LoadContent();

            screenWidth = GraphicsDevice.Viewport.Width;

            scoreFont = Content.Load<SpriteFont>(@"fonts\Arial");
            ActiveWeaponContainer.Initialize(screenWidth);
            ActiveWeaponContainer.LoadContent();
        }

        /// <summary>
        /// The load dynamic content.
        /// </summary>
        public static void LoadDynamicContent()
        {
            foreach (Floor floor in Floors.Where(f => f.Active))
            {
                floor.LoadContent();
                if (MainPlayer.Weapons.Count > 0)
                {
                    MainPlayer.ActiveWeapon.LoadContent();
                }
            }
        }

        /// <summary>
        /// The reset game time.
        /// </summary>
        public static void ResetGameTime()
        {
            gameTime = new TimeSpan(0, 0, 0);
        }

        /// <summary>
        /// The set content and device.
        /// </summary>
        /// <param name="content">
        /// The content.
        /// </param>
        /// <param name="graphicsDevice">
        /// The graphics device.
        /// </param>
        public static void SetContentAndDevice(ContentManager content, GraphicsDevice graphicsDevice)
        {
            Contract.Requires(content != null);
            Contract.Requires(graphicsDevice != null);
            Content = content;
            GraphicsDevice = graphicsDevice;
        }

        /// <summary>
        /// The store score.
        /// </summary>
        /// Author: Juan Mompean Esteban
        public static void StoreScore()
        {
            var stats = new Statistics();
            stats.ReadFile();
            foreach (Player player in Floors.SelectMany(floor => floor.Players))
            {
                stats.AddPlayer(player.Name);
                stats.AddScore(player.Name, player.Score);
                stats.AddTime(player.Name, (uint)gameTime.TotalSeconds);
                if (GameState == GameStateEnum.GameOver)
                {
                    stats.AddLost(player.Name);
                }
                else if (GameState == GameStateEnum.Win)
                {
                    stats.AddVictory(player.Name);
                }
            }

            Floors.Clear();
            stats.WriteFile();
        }

        /// <summary>
        /// The update.
        /// </summary>
        /// <param name="gameTime">
        /// The game time.
        /// </param>
        public static void Update(GameTime gameTime)
        {
            Contract.Requires(gameTime != null);
            foreach (Floor floor in Floors.Where(f => f.Active))
            {
                CreateEnemies(gameTime);
                CreateExtraObject();
                floor.Update(gameTime);
                if (MainPlayer.Weapons.Count > 0)
                {
                    ActiveWeaponContainer.Update(gameTime, floor.position, MainPlayer.ActiveWeapon);
                }
            }

            if (Floors.Any(f => f.Active))
            {
                Elevator.Update(gameTime, Floors.Where(f => f.Active).Single().position);
            }

            Client.gameTime += gameTime.ElapsedGameTime;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The create enemies.
        /// </summary>
        /// <param name="gameTime">
        /// The game time.
        /// </param>
        private static void CreateEnemies(GameTime gameTime)
        {
            if ((gameTime.TotalGameTime - previousSpawnTime) <= enemySpawnTime)
            {
                return;
            }

            previousSpawnTime = gameTime.TotalGameTime;
            foreach (Floor floor in
                Floors.Where(floor => floor.Active).Where(
                    floor => floor.NumberOfSpawnedEnemies < floor.MaxNumberOfEnemies))
            {
                string name = @"Enemies\zombie3";
                uint life = 100;
                uint damage = 9;
                uint numberFrames = 9;
                var timeToAttack = new TimeSpan(0, 0, 4);
                uint speed = 2;
                switch (floor.Number)
                {
                    case 3:
                        name = @"Enemies\zombie4";
                        life = 150;
                        damage = 12;
                        numberFrames = 4;
                        timeToAttack = new TimeSpan(0, 0, 3);
                        speed = 3;
                        break;
                    case 4:
                        name = @"Enemies\zombie5";
                        life = 200;
                        damage = 12;
                        numberFrames = 4;
                        timeToAttack = new TimeSpan(0, 0, 2);
                        speed = 3;
                        break;
                    case 5:
                        name = @"Enemies\bossJ";
                        life = 1000;
                        damage = 20;
                        numberFrames = 8;
                        timeToAttack = new TimeSpan(0, 0, 1);
                        speed = 4;
                        break;
                }

                floor.AddEnemy(
                    new Enemy(
                        life, name, numberFrames, damage, 8, floor.Number, speed, timeToAttack, gameTime.TotalGameTime));
                floor.NumberOfSpawnedEnemies += 1;
            }
        }

        /// <summary>
        /// The create extra object.
        /// </summary>
        private static void CreateExtraObject()
        {
            foreach (Floor floor in Floors.Where(f => f.Active))
            {
                var random = new Random();
                if (random.Next(1000) >= 4)
                {
                    continue;
                }

                var position = new Vector2(random.Next((int)floor.Width), random.Next(floor.Height));
                if (
                    floor.ExtraObjects.Exists(
                        extraObject => extraObject.Position.X == position.X && extraObject.Position.Y == position.Y))
                {
                    break;
                }

                switch (random.Next(4))
                {
                    case 0:
                        floor.AddExtraObject(
                            new Life(
                                @"Stuff\life", 1, floor.Number, position, (uint)random.Next(100) + 1, @"Sounds\TakeLife"));
                        break;
                    case 1:
                        floor.AddExtraObject(
                            new Money(
                                @"Stuff\coin", 1, floor.Number, position, (uint)random.Next(100) + 1, @"Sounds\TakeCoin"));
                        break;
                    case 2:
                        floor.AddExtraObject(
                            new Knife(@"Stuff\knife", 1, floor.Number, position, 5, 20, @"Sounds\Stab"));
                        break;
                    case 3:
                        floor.AddExtraObject(
                            new Firearm(
                                @"Stuff\firearm", 
                                1, 
                                floor.Number, 
                                position, 
                                50, 
                                new TimeSpan(0, 0, 0, 0, 300), 
                                @"Stuff\projectile", 
                                1, 
                                10, 
                                @"Sounds\Shot"));
                        break;
                }
            }
        }

        /// <summary>
        /// The draw active weapon.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        private static void DrawActiveWeapon(SpriteBatch spriteBatch)
        {
            ActiveWeaponContainer.Draw(spriteBatch);
        }

        /// <summary>
        /// The draw help.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        /// <param name="message">
        /// The message.
        /// </param>
        /// <param name="height">
        /// The height.
        /// </param>
        private static void DrawHelp(SpriteBatch spriteBatch, string message, float height)
        {
            var smallFont = Content.Load<SpriteFont>(@"fonts/small");

            // Draw Help for shop
            spriteBatch.DrawString(
                smallFont, 
                message, 
                new Vector2(screenWidth - smallFont.MeasureString(message).X, height), 
                Color.OrangeRed, 
                0, 
                Vector2.Zero, 
                1, 
                SpriteEffects.None, 
                1);
        }

        /// <summary>
        /// The draw texts.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        private static void DrawTexts(SpriteBatch spriteBatch)
        {
            // Draw Score
            spriteBatch.DrawString(
                scoreFont, 
                "Score: " + MainPlayer.Score, 
                new Vector2(10, 10), 
                Color.OrangeRed, 
                0, 
                Vector2.Zero, 
                1, 
                SpriteEffects.None, 
                1);

            // Draw Life
            spriteBatch.DrawString(
                scoreFont, 
                "Life: " + MainPlayer.Life, 
                new Vector2(10, 30), 
                Color.OrangeRed, 
                0, 
                Vector2.Zero, 
                1, 
                SpriteEffects.None, 
                1);

            // Draw Money
            spriteBatch.DrawString(
                scoreFont, 
                "Money: " + MainPlayer.Money, 
                new Vector2(10, 50), 
                Color.OrangeRed, 
                0, 
                Vector2.Zero, 
                1, 
                SpriteEffects.None, 
                1);
            DrawHelp(spriteBatch, "Press S to go to the Shop.", 10);
            DrawHelp(spriteBatch, "Press P to pause the game.", 30);
            DrawHelp(spriteBatch, "Press Q to change your active weapon.", 50);
            DrawHelp(spriteBatch, "Press Escape to exit the game.", 70);
        }

        #endregion
    }
}